/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.compomics.daubechiesfilter.filter;

import com.compomics.daubechiesfilter.statistics.SizeSampler;
import com.compomics.daubechiesfilter.statistics.ThresholdGenerator;
import com.compomics.daubechiesfilter.transformation.DaubechiesWaveletTransformer;
import com.compomics.util.experiment.massspectrometry.Spectrum;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.TreeMap;
import org.apache.log4j.Logger;

/**
 *
 * @author Kenneth
 */
public class DaubechiesFilter {

    private final static Logger logger = Logger.getLogger(DaubechiesFilter.class);
    private double[] intensities;
    private final static DaubechiesWaveletTransformer daub = new DaubechiesWaveletTransformer();
    private double[] mz;
    private Map<Double, Double> sortingMap = new TreeMap<Double, Double>();
    private Map<Double, Double> returnPeakMap = new TreeMap<Double, Double>();
    private int framesize = 32;
    private boolean filtered;

    public DaubechiesFilter(double[] mz, double[] intensities, int framesize) {
        this.intensities = intensities;
        this.mz = mz;
        this.framesize = framesize;
    }

    /**
     * Creates an instance of a Daubechies Wavelet Filter for a given spectrum.
     * This instance automatically calculates the sample size
     */
    public DaubechiesFilter(Spectrum spectrum) {
        this.mz = spectrum.getMzValuesAsArray();
        this.intensities = spectrum.getIntensityValuesAsArray();
        SizeSampler sampler = new SizeSampler(spectrum);
        this.framesize = sampler.calculateFrameSize();
    }

    /**
     * Creates an instance of a Daubechies Wavelet Filter for a given spectrum
     * with a set framesize
     */
    public DaubechiesFilter(Spectrum spectrum, int framesize) {
        this.mz = spectrum.getMzValuesAsArray();
        this.intensities = spectrum.getIntensityValuesAsArray();
        this.framesize = framesize;
    }

    /**
     * Returns the sorted map of mz values and intensities
     *
     */
    public Map<Double, Double> getFilteredMap() {
        return returnPeakMap;
    }

    /**
     * Retrieve the filtered mz-values as an array
     */
    public double[] getFilteredMzAsArray() {
        double[] mzValues = new double[returnPeakMap.size()];
        for (int i = 0; i < returnPeakMap.keySet().size(); i++) {
            mzValues[i] = (double) returnPeakMap.keySet().toArray()[i];
        }
        return mzValues;
    }

    /**
     * Retrieve the filtered intensities as an array
     */
    public double[] getFilteredIntensitiesAsArray() {
        double[] intensityValues = new double[returnPeakMap.size()];
        for (int i = 0; i < returnPeakMap.values().size(); i++) {
            intensityValues[i] = (double) returnPeakMap.values().toArray()[i];
        }
        return intensityValues;
    }

    /**
     * Execute the filtration fo the datapoints. This methods needs to precede
     * the retrieving of filtered data.
     */
    public void filterSpectrum() {
        if (!filtered) {
            logger.debug("Sorting mz values");
            sortSpectra();
            logger.debug("Splitting spectrum according to framesize : " + framesize);
            Map<double[], double[]> subSpectra = splitSpectrum(mz, intensities, framesize);
            logger.debug("Filtering peaks for " + subSpectra.size() + " subspectra");
            int i = 1;
            for (double[] aSubSpectrumMz : subSpectra.keySet()) {
                System.out.println("For subSpectrum " + i);
                transformFrame(aSubSpectrumMz, subSpectra.get(aSubSpectrumMz));
                i++;
            }
            logger.debug("Finished filtering of data !");
            filtered = true;
        } else {
            logger.error("This wavelet has already converted the spectrum");
        }
    }

    /**
     * Sorts the spectra to ensure that mz are continuously increasing values
     */
    private void sortSpectra() {
        //sorting the mz's
        for (int i = 0; i < mz.length; i++) {
            sortingMap.put(mz[i], intensities[i]);
        }

        Object[] mzArray = sortingMap.keySet().toArray();
        Object[] intensityArray = sortingMap.values().toArray();

        for (int i = 0; i < sortingMap.size(); i++) {
            this.mz[i] = (Double) mzArray[i];
            this.intensities[i] = (Double) intensityArray[i];
        }
    }

    /**
     * performs a Daubechies transformation on the subspectra
     */
    private void transformFrame(double[] subSpectrumMz, double[] subSpectrumIntensities) {
        double[] tempSpectra = subSpectrumIntensities.clone();
        daub.daubTrans(tempSpectra);
        double thresHoldIntensity = ThresholdGenerator.getThresHold(tempSpectra);
        System.out.println("Using treshold of : " + thresHoldIntensity);
        for (int y = 0; y <= subSpectrumMz.length; y++) {
            if (intensities[y] >= thresHoldIntensity) {
                returnPeakMap.put(subSpectrumMz[y], subSpectrumIntensities[y]);
            }
        }
    }

    /**
     * split up the spectrumvalues from the constructor in chunks according to
     * the framesize
     */
    private static LinkedHashMap<double[], double[]> splitSpectrum(double[] mz, double[] intensities, int max) {
        if (max == 0) {
            max = mz.length;
        }
        int x = mz.length / max;

        int lower = 0;
        int upper = 0;

        LinkedHashMap<double[], double[]> subSpectraMap = new LinkedHashMap<double[], double[]>();

        for (int i = 0; i < x; i++) {

            upper += max;

            subSpectraMap.put(Arrays.copyOfRange(mz, lower, upper), Arrays.copyOfRange(intensities, lower, upper));

            lower = upper;
        }

        if (upper < mz.length - 1) {

            lower = upper;

            upper = mz.length;

            subSpectraMap.put(Arrays.copyOfRange(mz, lower, upper), Arrays.copyOfRange(intensities, lower, upper));
        }

        return subSpectraMap;
    }
}
